﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Reflection;

namespace MVVMWrapper {
   /// <summary>
   /// Is a ViewModel Base class that propagates NotifyPropertyChanged messages
   /// </summary>
   public abstract class NotificationObject : INotifyPropertyChanged, IItemWrapper {

      /// <summary>
      /// Stores all properties of the object we are wrapping
      /// </summary>
      static Dictionary<Type, Collection<String>> PropertyDictionary;

      /// <summary>
      /// Initialises the Property Dictionary
      /// </summary>
      static NotificationObject() {
         NotificationObject.PropertyDictionary = new Dictionary<Type, Collection<string>>();
      }

      private Type _type;
      private INotifyPropertyChanged _wrappedObject;

      public NotificationObject() {
         _type = this.GetType();
         //if we have already added the properties for this object dont readd them
         if (NotificationObject.PropertyDictionary.ContainsKey(_type)) { }
         //add the properties for this object
         else {
            //add an entry to the dictionary
            NotificationObject.PropertyDictionary.Add(_type, new Collection<string>());

            foreach (PropertyInfo prop in _type.GetProperties()) {
               //only add the public properties
               if (prop.PropertyType.IsPublic) {
                  NotificationObject.PropertyDictionary[_type].Add(prop.Name);
                  //Debug.WriteLine("Prop: {0}", prop.Name);
               }
            }
         }
      }

      public NotificationObject(INotifyPropertyChanged wrappedObject)
         : this() {
         this.WrappedObject = wrappedObject;
      }

      /// <summary>
      /// Gets and Sets the Wrapped Object
      /// </summary>
      protected INotifyPropertyChanged WrappedObject {
         get { return _wrappedObject; }
         set {
            if (_wrappedObject != value) {
               
               //if we currently have one
               if (_wrappedObject != null) {
                  //unsubscribe to notification changed
                  ((INotifyPropertyChanged)_wrappedObject).PropertyChanged -= new PropertyChangedEventHandler(NotificationObject_PropertyChanged);
               }

               //assign
               _wrappedObject = value;

               if (_wrappedObject != null) {
                  //subscribe to notification changed
                  ((INotifyPropertyChanged)_wrappedObject).PropertyChanged += new PropertyChangedEventHandler(NotificationObject_PropertyChanged);
               }
            }
         }
      }

      void NotificationObject_PropertyChanged(object sender, PropertyChangedEventArgs e) {
         //if this object, and any that derive from it have a property by the same name as the one
         //in the NotifyPropertyChanged EventArgs, then raise Property Changed with the same PropertyName
         if (NotificationObject.PropertyDictionary[_type].Contains(e.PropertyName)) {
            //call a virtual method so custom handling can be done in derived classes, that determins if Notify should be called
            if (OnRaisingNotifyPropertyChanged(e.PropertyName)) {
               NotifyPropertyChanged(e.PropertyName);
            }
            //call the virtual to let derived classes know we have raised property changed
            OnRaisedPropertyChanged(e.PropertyName);
         }
      }

      /// <summary>
      /// Called after NotifyPropertyChanged is called
      /// </summary>
      /// <param name="propertyName">property name</param>
      protected virtual void OnRaisedPropertyChanged(String propertyName) { }

      /// <summary>
      /// Called before NotifyPropertyChanged happens to allow interception.
      /// </summary>
      /// <param name="propertyName">Name of the property that has changed</param>
      /// <returns>true if the NotifyPropertyChanged should proceed, false otherwise</returns>
      protected virtual Boolean OnRaisingNotifyPropertyChanged(String propertyName) {
         return true;
      }

      #region INotifyPropertyChanged Members

      /// <summary>
      /// Property Changed event
      /// </summary>
      public event PropertyChangedEventHandler PropertyChanged;

      /// <summary>
      /// Standard NotifyPropertyChanged Method, Override if you need to do special handling in your class
      /// </summary>
      /// <param name="propertyName">Property Name</param>
      protected virtual void NotifyPropertyChanged(string propertyName) {
         if (PropertyChanged != null) {
            PropertyChanged(this,
              new PropertyChangedEventArgs(propertyName));
         }
      }

      #endregion

      #region IItemWrapper Members

      public bool IsItemWrapper(INotifyPropertyChanged item) {
         return (this.WrappedObject == item);
      }

      #endregion
   }
}
